package middleware

import (
	"bytes"
	"context"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"gin-demo/common"
	"gin-demo/common/result"
	"io"
	"net/http"
	"regexp"
	"strings"
	"time"

	"github.com/bsm/redislock"
	"github.com/google/uuid"

	"github.com/redis/go-redis/v9"

	"github.com/casbin/casbin/v2"

	"github.com/gin-gonic/gin"
)

type responseWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer
}

func (rw *responseWriter) Write(b []byte) (int, error) {
	rw.body.Write(b)
	return rw.ResponseWriter.Write(b)
}

func (rw *responseWriter) WriteString(s string) (int, error) {
	rw.body.WriteString(s)
	return rw.ResponseWriter.WriteString(s)
}

func ErrorHandler(c *gin.Context) {
	c.Next()
	if len(c.Errors) > 0 {
		c.AbortWithStatusJSON(http.StatusInternalServerError, result.Abort(result.CodeInternalServerError, c.Errors.Last().Error()))
	}
}

func CustomRecovery(c *gin.Context, err any) {
	str := fmt.Sprintf("Recovered from a panic: %v", err)
	c.AbortWithStatusJSON(http.StatusInternalServerError, result.Abort(result.CodeInternalServerError, str))
}

func Authorization(e *casbin.Enforcer) gin.HandlerFunc {
	return func(c *gin.Context) {
		//sub := c.Query("username")
		role := c.GetString("role")
		if role == "" {
			c.AbortWithStatusJSON(http.StatusInternalServerError, result.Abort(result.CodeInternalServerError, "角色不存在"))
			return
		}
		obj := c.Request.URL.Path

		excludePathMap := common.GetExcludePathMap()
		if _, ok := excludePathMap[obj]; ok {
			c.Next()
			return
		}
		requestFuncName := obj[strings.LastIndex(obj, "/")+1:]
		preStr := regexp.MustCompile("^[a-z]+").FindString(requestFuncName)
		act := ""
		switch {
		case preStr == "get", preStr == "select", preStr == "query", preStr == "list":
			act = "read"
		case preStr == "update", preStr == "add", preStr == "save", preStr == "delete":
			act = "write"
		default:
			act = ""
		}
		roles := strings.Split(role, ",")
		for _, sub := range roles {
			if ok, err := e.Enforce(sub, obj, act); err != nil {
				c.AbortWithStatusJSON(http.StatusInternalServerError, result.Abort(result.CodeInternalServerError, err.Error()))
				return
			} else if !ok {
				c.AbortWithStatusJSON(http.StatusForbidden, result.Abort(result.CodeForbidden, "没有访问权限"))
				return
			} else if ok {
				break
			}
		}
		c.Next()
	}
}

func Authentication(c *gin.Context) {
	path := c.Request.URL.Path
	excludePathMap := common.GetExcludePathMap()
	if _, ok := excludePathMap[path]; ok {
		c.Next()
		return
	}
	token, err := c.Cookie("token")
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, result.Abort(result.CodeInternalServerError, err.Error()))
		return
	}
	jwtClaims, err := common.VerifyToken(token)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusUnauthorized, result.Abort(result.CodeUnauthorized, err.Error()))
		return
	}
	c.Set("role", jwtClaims.Role)
	c.Next()
}

func RedisCache(c *gin.Context) {
	cacheUrlMap := make(map[string]time.Duration)
	cacheUrlMap["/api/getUserById"] = 0
	if _, ok := cacheUrlMap[c.Request.URL.Path]; !ok {
		c.Next()
		return
	}

	writer := &responseWriter{ResponseWriter: c.Writer, body: bytes.NewBufferString("")}
	c.Writer = writer

	var params []byte
	if c.Request.Method == "GET" {
		query := c.Request.URL.Query()
		marshal, err := json.Marshal(query)
		if err != nil {
			c.AbortWithStatusJSON(http.StatusInternalServerError, result.Abort(result.CodeInternalServerError, err.Error()))
			return
		}
		params = marshal
	} else if c.Request.Method == "POST" {
		query := c.Request.PostForm
		marshal, err := json.Marshal(query)
		if err != nil {
			c.AbortWithStatusJSON(http.StatusInternalServerError, result.Abort(result.CodeInternalServerError, err.Error()))
			return
		}
		params = marshal
	}

	hash := md5.New()
	hash.Write(params)
	md5Str := hex.EncodeToString(hash.Sum(nil))
	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()
	key := strings.Join([]string{"gin", c.Request.URL.Path, md5Str}, ":")
	rdb := common.RedisDB()
	data, err := rdb.Get(ctx, key).Result()
	if err == nil {
		r := result.Result{}
		err := json.Unmarshal([]byte(data), &r)
		if err != nil {
			c.AbortWithStatusJSON(http.StatusInternalServerError, result.Abort(result.CodeInternalServerError, err.Error()))
			return
		}
		c.JSON(http.StatusOK, r)
		c.Abort()
		return
	} else if !errors.Is(err, redis.Nil) {
		c.AbortWithStatusJSON(http.StatusInternalServerError, result.Abort(result.CodeInternalServerError, err.Error()))
		return
	}
	c.Next()

	// 分布式锁
	locker := redislock.New(rdb)
	token := uuid.New().String()
	options := &redislock.Options{
		RetryStrategy: redislock.LinearBackoff(1 * time.Second),
		Token:         token,
	}
	lock, err := locker.Obtain(ctx, strings.Join([]string{key, "lock"}, "-"), 15*time.Second, options)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, result.Abort(result.CodeInternalServerError, err.Error()))
		return
	}
	defer func(lock *redislock.Lock, ctx context.Context) {
		if lock.Token() == token {
			err := lock.Release(ctx)
			if err != nil {
				panic(err)
			}
		}
	}(lock, ctx)

	// 获取分布式锁之后再次查询redis
	data, err = rdb.Get(ctx, key).Result()
	if err == nil {
		r := result.Result{}
		err := json.Unmarshal([]byte(data), &r)
		if err != nil {
			c.AbortWithStatusJSON(http.StatusInternalServerError, result.Abort(result.CodeInternalServerError, err.Error()))
			return
		}
		c.JSON(http.StatusOK, r)
		c.Abort()
		return
	} else if !errors.Is(err, redis.Nil) {
		c.AbortWithStatusJSON(http.StatusInternalServerError, result.Abort(result.CodeInternalServerError, err.Error()))
		return
	}

	// 获取锁后再次查redis没有缓存, 就继续执行
	bodyData, err := io.ReadAll(writer.body)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, result.Abort(result.CodeInternalServerError, err.Error()))
		return
	}
	if c.Writer.Status() == http.StatusOK {
		rdb.Set(ctx, key, string(bodyData), cacheUrlMap[c.Request.URL.Path])
	}
}
